View Javadoc

1   /*
2    * J.A.D.E. Java(TM) Addition to Default Environment.
3    * Latest release available at http://jade.dautelle.com/
4    * This class is public domain (not copyrighted).
5    */
6   package ch.twiddlefinger.inet.rewinder.model.parser.conversion;
7   
8   import java.util.Collection;
9   import java.util.Collections;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.Set;
13  
14  
15  /***
16   * <p> This class provides cache access to <code>Map</code> collections.</p>
17   * 
18   * <p> Instance of this class can be used as "proxy" for any collection
19   *     implementing the <code>java.util.Map</code> interface.</p>
20   * 
21   * <p> Typically, {@link CachedMap} are used to accelerate access to
22   *     large collections when the access to the collection is not evenly 
23   *     distributed (associative cache). The performance gain is about
24   *     50% for the fastest hash map collections (e.g. {@link FastMap}). 
25   *     For slower collections such as <code>java.util.TreeMap</code>, 
26   *     non-resizable {@link FastMap} (real-time) or database access,
27   *     performance can be of several orders of magnitude.</p>
28   * 
29   * <p> <b>Note:</b> The keys used to access elements of a {@link CachedMap} do
30   *     not need to be immutable as they are not stored in the cache
31   *     (only keys specified by the {@link #put} method are).
32   *     In other words, access can be performed using mutable keys as long
33   *     as these keys can be compared for equality with the real map's keys
34   *     (e.g. same <code>hashCode</code> values).</p>
35   * 
36   * <p> This implementation is not synchronized. Multiple threads accessing
37   *     or modifying the collection must be synchronized externally.</p>
38   *
39   * <p><i> This class is <b>public domain</b> (not copyrighted).</i></p>
40   * 
41   * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
42   * @version 5.3, October 30, 2003
43   */
44  public final class CachedMap implements Map {
45      /***
46   * Holds the FastMap backing this collection
47   * (<code>null</code> if generic backing map).
48   */
49      private final FastMap _backingFastMap;
50  
51      /***
52   * Holds the generic map backing this collection.
53   */
54      private final Map _backingMap;
55  
56      /***
57   * Holds the keys of the backing map (key-to-key mapping).
58   * (<code>null</code> if FastMap backing map).
59   */
60      private final FastMap _keysMap;
61  
62      /***
63   * Holds the cache's mask (capacity - 1).
64   */
65      private final int _mask;
66  
67      /***
68   * Holds the keys being cached.
69   */
70      private final Object[] _keys;
71  
72      /***
73   * Holds the values being cached.
74   */
75      private final Object[] _values;
76  
77      /***
78   * Creates a cached map backed by a {@link FastMap}.
79   * The default cache size and map capacity is set to <code>256</code>
80   * entries.
81   */
82      public CachedMap() {
83          this(256, new FastMap());
84      }
85  
86      /***
87   * Creates a cached map backed by a {@link FastMap} and having the 
88   * specified cache size.
89   *
90   * @param  cacheSize the cache size, the actual cache size is the
91   *         first power of 2 greater or equal to <code>cacheSize</code>.
92   *         This is also the initial capacity of the backing map.
93   */
94      public CachedMap(int cacheSize) {
95          this(cacheSize, new FastMap(cacheSize));
96      }
97  
98      /***
99   * Creates a cached map backed by the specified map and having the specified
100  * cache size. In order to maitain cache veracity, it is critical
101  * that <b>all</b> update to the backing map is accomplished through the
102  * {@link CachedMap} instance; otherwise {@link #flush} has to be called.
103  *
104  * @param  cacheSize the cache size, the actual cache size is the
105  *         first power of 2 greater or equal to <code>cacheSize</code>.
106  * @param  backingMap the backing map to be "wrapped" in a cached map.
107  */
108     public CachedMap(int cacheSize, Map backingMap) {
109         // Find a power of 2 >= minimalCache
110         int actualCacheSize = 1;
111 
112         while (actualCacheSize < cacheSize) {
113             actualCacheSize <<= 1;
114         }
115 
116         // Sets up cache.
117         _keys = new Object[actualCacheSize];
118         _values = new Object[actualCacheSize];
119         _mask = actualCacheSize - 1;
120 
121         // Sets backing map references.
122         if (backingMap instanceof FastMap) {
123             _backingFastMap = (FastMap) backingMap;
124             _backingMap = _backingFastMap;
125             _keysMap = null;
126         } else {
127             _backingFastMap = null;
128             _backingMap = backingMap;
129             _keysMap = new FastMap(backingMap.size());
130 
131             for (Iterator i = backingMap.keySet().iterator(); i.hasNext();) {
132                 Object key = i.next();
133                 _keysMap.put(key, key);
134             }
135         }
136     }
137 
138     /***
139  * Returns the actual cache size.
140  *
141  * @return the cache size (power of 2).
142  */
143     public int getCacheSize() {
144         return _keys.length;
145     }
146 
147     /***
148  * Returns the backing map. If the backing map is modified directly,
149  * this {@link CachedMap} has to be flushed.
150  *
151  * @return  the backing map.
152  * @see    #flush
153  */
154     public Map getBackingMap() {
155         return (_backingFastMap != null) ? _backingFastMap : _backingMap;
156     }
157 
158     /***
159  * Flushes the key/value pairs being cached. This method should be called
160  * if the backing map is externally modified.
161  */
162     public void flush() {
163         for (int i = 0; i < _keys.length; i++) {
164             _keys[i] = null;
165             _values[i] = null;
166         }
167 
168         if (_keysMap != null) {
169             // Re-populates keys from backing map.
170             for (Iterator i = _backingMap.keySet().iterator(); i.hasNext();) {
171                 Object key = i.next();
172                 _keysMap.put(key, key);
173             }
174         }
175     }
176 
177     /***
178  * Returns the value to which this map maps the specified key.
179  * First, the cache is being checked, then if the cache does not contains
180  * the specified key, the backing map is accessed and the key/value
181  * pair is stored in the cache.
182  *
183  * @param  key the key whose associated value is to be returned.
184  * @return the value to which this map maps the specified key, or
185  *               <code>null</code> if the map contains no mapping for this key.
186  * @throws ClassCastException if the key is of an inappropriate type for
187  *         the backing map (optional).
188  * @throws NullPointerException if the key is <code>null</code>.
189  */
190     public Object get(Object key) {
191         int index = key.hashCode() & _mask;
192 
193         return key.equals(_keys[index]) ? _values[index]
194                                         : getCacheMissed(key, index);
195     }
196 
197     private Object getCacheMissed(Object key, int index) {
198         if (_backingFastMap != null) {
199             Entry entry = _backingFastMap.getEntry(key);
200 
201             if (entry != null) {
202                 _keys[index] = entry.getKey();
203 
204                 Object value = entry.getValue();
205                 _values[index] = value;
206 
207                 return value;
208             } else {
209                 return null;
210             }
211         } else { // Generic backing map.
212 
213             Object mapKey = _keysMap.get(key);
214 
215             if (mapKey != null) {
216                 _keys[index] = mapKey;
217 
218                 Object value = _backingMap.get(key);
219                 _values[index] = value;
220 
221                 return value;
222             } else {
223                 return null;
224             }
225         }
226     }
227 
228     /***
229  * Associates the specified value with the specified key in this map.
230  *
231  * @param  key the key with which the specified value is to be associated.
232  * @param  value the value to be associated with the specified key.
233  * @return the previous value associated with specified key, or
234  *         <code>null</code> if there was no mapping for the key.
235  * @throws UnsupportedOperationException if the <code>put</code> operation
236  *         is not supported by the backing map.
237  * @throws ClassCastException if the class of the specified key or value
238  *                prevents it from being stored in this map.
239  * @throws IllegalArgumentException if some aspect of this key or value
240  *               prevents it from being stored in this map.
241  * @throws NullPointerException if the key is <code>null</code>.
242  */
243     public Object put(Object key, Object value) {
244         // Updates the cache.
245         int index = key.hashCode() & _mask;
246 
247         if (key.equals(_keys[index])) {
248             _values[index] = value;
249         } else if (_keysMap != null) { // Possibly a new key.
250             _keysMap.put(key, key);
251         }
252 
253         // Updates the backing map.
254         return _backingMap.put(key, value);
255     }
256 
257     /***
258  * Removes the mapping for this key from this map if it is present.
259  *
260  * @param  key key whose mapping is to be removed from the map.
261  * @return previous value associated with specified key,
262  *         or <code>null</code> if there was no mapping for key.
263  * @throws ClassCastException if the key is of an inappropriate type for
264  *                the backing map (optional).
265  * @throws NullPointerException if the key is <code>null</code>.
266  * @throws UnsupportedOperationException if the <code>remove</code> method
267  *         is not supported by the backing map.
268  */
269     public Object remove(Object key) {
270         // Removes from cache.
271         int index = key.hashCode() & _mask;
272 
273         if (key.equals(_keys[index])) {
274             _keys[index] = null;
275         }
276 
277         // Removes from key map.
278         if (_keysMap != null) {
279             _keysMap.remove(key);
280         }
281 
282         // Removes from backing map.
283         return _backingMap.remove(key);
284     }
285 
286     /***
287  * Indicates if this map contains a mapping for the specified key.
288  *
289  * @param   key the key whose presence in this map is to be tested.
290  * @return <code>true</code> if this map contains a mapping for the
291  *         specified key; <code>false</code> otherwise.
292  */
293     public boolean containsKey(Object key) {
294         // Checks the cache.
295         int index = key.hashCode() & _mask;
296 
297         if (key.equals(_keys[index])) {
298             return true;
299         } else { // Checks the backing map.
300 
301             return _backingMap.containsKey(key);
302         }
303     }
304 
305     /***
306  * Returns the number of key-value mappings in this map.  If the
307  * map contains more than <code>Integer.MAX_VALUE</code> elements,
308  * returns <code>Integer.MAX_VALUE</code>.
309  *
310  * @return the number of key-value mappings in this map.
311  */
312     public int size() {
313         return _backingMap.size();
314     }
315 
316     /***
317  * Returns <code>true</code> if this map contains no key-value mappings.
318  *
319  * @return <code>true</code> if this map contains no key-value mappings.
320  */
321     public boolean isEmpty() {
322         return _backingMap.isEmpty();
323     }
324 
325     /***
326  * Returns <code>true</code> if this map maps one or more keys to the
327  * specified value.
328  *
329  * @param  value value whose presence in this map is to be tested.
330  * @return <code>true</code> if this map maps one or more keys to the
331  *         specified value.
332  * @throws ClassCastException if the value is of an inappropriate type for
333  *                the backing map (optional).
334  * @throws NullPointerException if the value is <code>null</code> and the
335  *         backing map does not not permit <code>null</code> values.
336  */
337     public boolean containsValue(Object value) {
338         return _backingMap.containsValue(value);
339     }
340 
341     /***
342  * Copies all of the mappings from the specified map to this map
343  * (optional operation).  This method automatically flushes the cache.
344  *
345  * @param map the mappings to be stored in this map.
346  * @throws UnsupportedOperationException if the <code>putAll</code> method
347  *         is not supported by the backing map.
348  * @throws ClassCastException if the class of a key or value in the
349  *                specified map prevents it from being stored in this map.
350  * @throws IllegalArgumentException some aspect of a key or value in the
351  *               specified map prevents it from being stored in this map.
352  * @throws NullPointerException the specified map is <code>null</code>, or
353  *         if the backing map does not permit <code>null</code> keys or
354  *         values, and the specified map contains <code>null</code> keys or
355  *         values.
356  */
357     public void putAll(Map map) {
358         _backingMap.putAll(map);
359         flush();
360     }
361 
362     /***
363  * Removes all mappings from this map (optional operation). This method
364  * automatically flushes the cache.
365  *
366  * @throws UnsupportedOperationException if clear is not supported by the
367  *                backing map.
368  */
369     public void clear() {
370         _backingMap.clear();
371         flush();
372     }
373 
374     /***
375  * Returns an <b>unmodifiable</b> view of the keys contained in this
376  * map.
377  *
378  * @return an unmodifiable view of the keys contained in this map.
379  */
380     public Set keySet() {
381         return Collections.unmodifiableSet(_backingMap.keySet());
382     }
383 
384     /***
385  * Returns an <b>unmodifiable</b> view of the values contained in this map.
386  *
387  * @return an unmodifiable view of the values contained in this map.
388  */
389     public Collection values() {
390         return Collections.unmodifiableCollection(_backingMap.values());
391     }
392 
393     /***
394  * Returns an <b>unmodifiable</b> view of the mappings contained in this
395  * map.  Each element in the returned set is a <code>Map.Entry</code>.
396  *
397  * @return an unmodifiable view of the mappings contained in this map.
398  */
399     public Set entrySet() {
400         return Collections.unmodifiableSet(_backingMap.entrySet());
401     }
402 
403     /***
404  * Compares the specified object with this map for equality.  Returns
405  * <tt>true</tt> if the given object is also a map and the two Maps
406  * represent the same mappings.
407  *
408  * @param  o object to be compared for equality with this map.
409  * @return <code>true</code> if the specified object is equal to this map.
410  */
411     public boolean equals(Object o) {
412         return _backingMap.equals(o);
413     }
414 
415     /***
416  * Returns the hash code value for this map.
417  *
418  * @return the hash code value for this map.
419  */
420     public int hashCode() {
421         return _backingMap.hashCode();
422     }
423 }